package com.googlecode.lohr.parser.combinators.node;

import java.lang.reflect.Method;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.googlecode.lohr.parser.combinators.matcher.Matcher;

public abstract class AbstractNode {

	private List<AbstractNode> children;

	/**
	 * The matcher that created this node.
	 */
	protected Matcher _matcher;

	/**
	 * A String representation of the associated grammar element. This text is
	 * not necessarily the same as the contents of the parsed docment.
	 */
	protected String text;

	/**
	 * The beginning position within the parsed document of the grammar element
	 * represented by this node
	 */
	protected int start = -2;

	/**
	 * The ending position, plus one, of the grammar element represented by this
	 * node in the original parsed document.
	 */
	protected int end = -2;

	public AbstractNode( Matcher matcher, final int start, final int end, String text) {
		assert start <= end;
		this.start = start;
		this.end = end;
		this.text = text;
		_matcher = matcher;
	}
	public AbstractNode( Matcher matcher, final int start, final int end) {
		assert start <= end;
		this.start = start;
		this.end = end;
		_matcher = matcher;
	}

	protected AbstractNode() {
	}

	public int length() {
		return end - start;
	}

	public int getStart() {
		return start;
	}

	public void setChildren(List<AbstractNode> children) {
		setChildren(children.toArray(new AbstractNode[children.size()]));
	}

	public void setChildren(AbstractNode[] children) {
		if (this.children != null)
			throw new RuntimeException("Children may only be set once");
		this.children = new ArrayList<AbstractNode>();
		int i= 0;
		for (AbstractNode c : children) {
			if (i == 0 && c.getStart() != start) 
					throw new RuntimeException("Start position of first child node is not consistent with node");
			if (i == children.length-1 && c.getEnd() != end) 
				throw new RuntimeException("End position of last child node is not consistent with node");
			this.children.add(c);
			i++;
		}
	}

	public void setStart(int value) {
		if (value <= -2)
			throw new InvalidParameterException();
		if (-2 < start)
			throw new RuntimeException("start may only be set once");
		start = value;
	}

	public void setEnd(int value) {
		if (value <= -2)
			throw new InvalidParameterException();
		if (-2 < start)
			throw new RuntimeException("end may only be set once");
		end = value;
	}

	public List<AbstractNode> getChildren() {
		if (children == null)
			return Collections.emptyList();
		return children;
	}

	public String getText() {
		if (this.text != null)
			return this.text;

		final StringBuilder builder = new StringBuilder();

		if (children != null) {
			for (final AbstractNode node : this.children) {
				builder.append(node.getText());
			}
		}

		return builder.toString();
	}

	@Override
	public String toString() {
		return "{" + _matcher.getLabel() + ":"+getText()+"}";
	}

	public void accept(NodeVistor visitor) {

		Method visitMethod = VisitorUtils.findVisitMethod(visitor, this);
		Boolean visitChildren;
		try {
			visitChildren = (Boolean) visitMethod.invoke(visitor,
					new Object[] { this });
		} catch (Exception e) {
			throw new RuntimeException("Internal Error", e);
		}
		if (visitChildren) {
			for (AbstractNode node : children)
				node.accept(visitor);
		}

		Method leaveMethod = VisitorUtils.findLeaveMethod(visitor, this);
		try {
			leaveMethod.invoke(visitor, new Object[] { this });
		} catch (Exception e) {
			throw new RuntimeException("Internal Error", e);
		}
	}

	public void setMatcher(Matcher matcher) {
		if (_matcher != null)
			throw new RuntimeException("matcher may only be set once");
		_matcher= matcher;
	}

	public Matcher getMatcher() {
		return _matcher;
	}

	public int getEnd() {
		return end;
	}
	
}
